package org.flashbrighton.as3bots.controllers
{
	import Box2D.Collision.Shapes.b2CircleShape;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	
	import flash.display.*;
	import flash.events.*;
	import flash.geom.ColorTransform;
	import flash.geom.Point;
	
	import org.flashbrighton.as3bots.bots.*;
	import org.flashbrighton.as3bots.components.*;
	import org.flashbrighton.as3bots.effects.*;
	import org.flashbrighton.as3bots.events.*;
	import org.flashbrighton.as3bots.models.*;
	import org.flashbrighton.as3bots.utils.MathUtils;
	import org.flashbrighton.as3bots.views.*;
	import org.flexunit.runner.manipulation.filters.AbstractFilter;
	
	
	public class GameController extends EventDispatcher
	{
		private static const SCAN_TIMER:int= 30;
		
		private var _model:GameModel;
		private var _view:GameView;
		private var _componentManager:ComponentManager;
		private var _soundController:SoundController;
		private var _arenaController:ArenaController;
		private var _matchController:MatchController;
		private var _scorer:CollisionScorer;
		private var _currentArenaWidth:Number;
		private var _gemScanTimer:int = 0;
		private var _bombScanTimer:int = 0;
		
		
		public function GameController(model:GameModel, view:GameView):void
		{
			_model = model;
			_view = view;
			init();
		}
		
		
		private function init():void
		{
			_componentManager = new ComponentManager();
			_arenaController = new ArenaController(_model, _view.arenaView);
			_soundController = new SoundController();
			_matchController = new MatchController(_model, _view);
			_scorer = new CollisionScorer();
			_scorer.addEventListener(BotEvent.HIT, checkCollisions, false, 0, true);
			_model.physics.world.SetContactListener(_scorer);
			_model.isPlaying = false;
			_model.addEventListener(DeadBotEvent.DEAD, onDeadBot);
		}
		
		protected function onDeadBot(e:DeadBotEvent):void
		{	
			_soundController.playDeadBot();
			
			// Add a gem at the bot location
			var point:Point = new Point();
			point.x = e.botModel.body.GetPosition().x;
			point.y = e.botModel.body.GetPosition().y;
			
			var gemModel:GemModel = addGem(500, point, 0xFFF200);
			_view.addGemView(gemModel.sprite, gemModel);
		}
		
		public function addBot(bot:BaseBot):void
		{
			var botModel:BotModel;
			
			// Add components
			for each (var s:String in bot.components)
			{
				_componentManager.addToBot(s, bot);
			}
			
			botModel = new BotModel(_model.physics.addCircle(0, 0, ChassisComponent.getRadiusFromSize(bot.size), _componentManager.chassis.density, _componentManager.chassis.friction, _componentManager.chassis.restitution));
			botModel.bot = bot;
			botModel.energy = HealthComponent.START_ENERGY;
			botModel.body.SetLinearDamping(_componentManager.chassis.linDamping);
			botModel.body.SetAngularDamping(_componentManager.chassis.angDamping);
			
			// Set reference back to model from body
			botModel.body.SetUserData(botModel);
			
			_model.addBot(botModel);
		}
		
		public function addGem(bonus:int, position:Point=null, color:uint=0):GemModel
		{
			var radius:Number = 10/GameView.SCALE_FACTOR;
			var body:b2Body = _model.physics.addCircle(0, 0, radius, 10, 0.3, 0.1);
			if(position!=null){
				body.SetPosition(new b2Vec2(position.x, position.y));
			}
			var sprite:Sprite = new GemGraphic();
			sprite.scaleX = GameView.SCALE_FACTOR * (radius * 2)/sprite.width;
			sprite.scaleY = sprite.scaleX;
			if(color!=0){
				var ct:ColorTransform = new ColorTransform();
				ct.color = color;
				sprite.transform.colorTransform = ct;
			}
			var gemModel:GemModel = new GemModel(body, sprite, bonus, position);
			body.SetUserData(gemModel);
			
			_model.addGem(gemModel);
			return gemModel;
		}

		public function addBomb(damage:int, position:Point=null, color:uint=0):BombModel
		{
			var radius:Number = 10/GameView.SCALE_FACTOR;
			var body:b2Body = _model.physics.addCircle(0, 0, radius, 10, 0.3, 0.1);
			if(position!=null){
				body.SetPosition(new b2Vec2(position.x, position.y));
			}
			var sprite:Sprite = new BombGraphic();
			sprite.scaleX = GameView.SCALE_FACTOR * (radius * 2)/sprite.width;
			sprite.scaleY = sprite.scaleX;
			if(color!=0){
				var ct:ColorTransform = new ColorTransform();
				ct.color = color;
				sprite.transform.colorTransform = ct;
			}
			var bombModel:BombModel = new BombModel(body, sprite, damage, position);
			body.SetUserData(bombModel);
			
			_model.addBomb(bombModel);
			return bombModel;
		}

		public function setupGame():void
		{
			_currentArenaWidth = ArenaController.START_ARENA_WIDTH;
			_arenaController.createWalls(_currentArenaWidth);
			_arenaController.arrangeBots(_currentArenaWidth, _currentArenaWidth);
			
			// add some gems @ 50pts
			for (var i:int = 0; i < _model.botList.length; i++) 
			{
				addGem(50);
				addGem(200, null, 0x00FF00);
			}			
			_arenaController.arrangeGems(_currentArenaWidth, _currentArenaWidth);
			
			
			// add some bombs @ -400pts
			for (var j:int = 0; j < _model.botList.length; j++) 
			{
				addBomb(400, null, 0xFF0000);
			}			
			_arenaController.arrangeBombs(_currentArenaWidth, _currentArenaWidth);
			
			addViews();
			_view.update();
			
			updateBotControllers();
			
			_matchController.addEventListener(MatchEndEvent.MATCH_END, endGame);
			_matchController.timer.addEventListener(TimerEvent.TIMER, shrinkArena);
			_matchController.timer.reset();
			
			_model.started = false;
		}
		
		
		public function startGame():void
		{
			_model.started = true;
			_model.isPlaying = true;
			_matchController.timer.start();
		}
		
		
		public function stopGame():void
		{
			_model.isPlaying = false;
			_matchController.timer.stop();
		}
		
		
		public function pauseGame():void
		{
			_model.isPlaying = false;
			_matchController.timer.stop();
		}
		
		
		public function resumeGame():void
		{
			_model.isPlaying = true;
			_matchController.timer.start();
		}
		
		
		private function addViews():void
		{
			var bot:BaseBot;
			var botModel:BotModel;
			var gemModel:GemModel;
			var bombModel:BombModel;
			
			_view.addArenaView();
			
			for each (botModel in _model.botList)
			{
				bot = botModel.bot;
				if (!bot.skin) bot.skin = ChassisComponent.createSkin(bot.size, bot.colour, bot.skin);
				_view.addBotView(bot.skin, botModel);
			}
			
			// add gem views
			for each (gemModel in _model.gemList)
			{
				_view.addGemView(gemModel.sprite, gemModel);
			}

			// add bomb views
			for each (bombModel in _model.bombList)
			{
				_view.addBombView(bombModel.sprite, bombModel);
			}
			
			_view.setUI();
			_view.addCountdown(_matchController.timer, MatchController.MAX_TIME);
			_view.alert.addEventListener(Event.COMPLETE, alertCompleteHandler);
			
			
		}
		
		
		private function alertCompleteHandler(e:Event):void
		{
			if (_model.started) resumeGame();
		}
		
		
		private function shrinkArena(e:TimerEvent):void
		{
			var shrinkRate:Number = 0.25;
			var minWidth:Number = 3.0;
			var interval:Number = 15;
			var dangerTime:int = 20;
			var remaining:int = MatchController.MAX_TIME - _matchController.timer.currentCount;
			
			if (_currentArenaWidth <= minWidth) return;
			
			if (remaining == dangerTime)
			{
				pauseGame();
				_view.alert.blink("DANGER");
				_soundController.playShrinkArena()
			}
			else if (remaining % interval == 0 && remaining > dangerTime)
			{
				pauseGame();
				_view.alert.blink("SHRINKING ARENA");
				_soundController.playShrinkArena()
			}
			
			if (remaining % interval == 0 || remaining <= dangerTime)
			{
				_soundController.playShrinkArena()
				_arenaController.destroyWalls();
				_arenaController.createWalls(_currentArenaWidth -= shrinkRate);
			}
		}
		
		
		/**
		 * The main loop.
		 */
		public function update(event:Event=null):void
		{
			if (!_model.isPlaying) return;
			
			_model.updateWorld();
			checkScanBots();
			checkScanGems();
			checkScanBombs();
			//checkCollisions is called at this point
			_view.update();
			updateValues();
			updateComponents();
			updateBotControllers();
			_matchController.update();
		}
		
		private function checkScanGems():void
		{
			// temporisation des scans de gems
			_gemScanTimer--;
			if(_gemScanTimer>0){
				return;
			}else{
				_gemScanTimer = SCAN_TIMER;
			}
			
			var curBotModel:BotModel;
			var gemModel:GemModel;
			var nearestGemModel:GemModel;
			var curPosition:Point;
			var position:Point;
			var curDist:Number;
			var dist:Number;
			var direction:Number;
			var evt:GemSensorEvent;
			var gemPositionArray:Array;
			
			for each (curBotModel in _model.botList)
			{
				curDist = 1000;
				nearestGemModel = null;
				curPosition = curBotModel.position;
				gemPositionArray = new Array();
				for each (gemModel in _model.gemList)
				{
					dist = Point.distance(curPosition, gemModel.position);
					if(dist < curDist){
						nearestGemModel = gemModel;
						curDist = dist;
					}
					position = gemModel.position;
					direction = MathUtils.getDirection(curBotModel, position.x, position.y);
					gemPositionArray.push(new GemPosition(direction, dist, gemModel.bonus));
				}
				if(nearestGemModel!=null){
					position = nearestGemModel.position;
					direction = MathUtils.getDirection(curBotModel, position.x, position.y);
					evt = new GemSensorEvent(direction, curDist, nearestGemModel.bonus, gemPositionArray);
					curBotModel.bot.controller.onGemSensor(evt);
				}
			}
			
		}		

		private function checkScanBombs():void
		{
			// temporisation des scans de bombs
			_bombScanTimer--;
			if(_bombScanTimer>0){
				return;
			}else{
				_bombScanTimer = SCAN_TIMER;
			}
			
			var curBotModel:BotModel;
			var bombModel:BombModel;
			var nearestBombModel:BombModel;
			var curPosition:Point;
			var position:Point;
			var curDist:Number;
			var dist:Number;
			var direction:Number;
			
			for each (curBotModel in _model.botList)
			{
				curDist = 1000;
				nearestBombModel = null;
				curPosition = curBotModel.position;
				
				
				// Detect nearest bomb
				for each (bombModel in _model.bombList)
				{
					dist = Point.distance(curPosition, bombModel.position);
					if(dist < curDist){
						nearestBombModel = bombModel;
						curDist = dist;
					}
					position = bombModel.position;
					direction = MathUtils.getDirection(curBotModel, position.x, position.y);
				}
				if(nearestBombModel!=null){
					position = nearestBombModel.position;
					direction = MathUtils.getDirection(curBotModel, position.x, position.y);
					curBotModel.bot.controller.onBombSensor(new BombSensorEvent(direction, curDist));
				}
				
			}
			
		}		
		
		private function checkScanBots():void
		{
			var curBotModel:BotModel;
			var botModel:BotModel;
			var nearestBotModel:BotModel;
			var nearestBot:BaseBot;
			var curPosition:Point;
			var position:Point;
			var curDist:Number;
			var dist:Number;
			var enginePower:Number;
			var steeringPower:Number;
			var direction:Number;
			var evt:ScanBotEvent;
			
			for each (curBotModel in _model.botList)
			{
				curPosition = curBotModel.position;
				if(curBotModel.scanReady){
					curDist = 1000;
					for each (botModel in _model.botList)
					{
						if(botModel!=curBotModel){
							dist = Point.distance(curPosition, botModel.position);
							if(dist < curDist){
								nearestBotModel = botModel;
								curDist = dist;
							}
						}
					}
					nearestBot = nearestBotModel.bot;
					enginePower = nearestBot.controller.getEnginePower();
					steeringPower = nearestBot.controller.getSteeringPower();
					position = nearestBotModel.position;
					direction = MathUtils.getDirection(curBotModel, position.x, position.y);
					evt = new ScanBotEvent(direction, curDist, nearestBot.botName);	
					curBotModel.bot.controller.onScanBot(evt);
				}
			}
			
		}		
		
		private function checkCollisions(e:CollisionEvent=null):void
		{
			var botModel:BotModel = e.botModel as BotModel;
			var bot:BaseBot = botModel.bot;
			var damage:int = int(e.damage);
			
			botModel.damage = damage;
			
			// Notify Bot
			if(e.targetType ==  CollidableType.WALL){
				_soundController.playWallCollision();
				bot.controller.onHitWall(new HitWallEvent(e.collisionPoint));
			}else if(e.targetType ==  CollidableType.BOT){
				_soundController.playBotCollision();
				bot.controller.onHitBot(new HitBotEvent(e.collisionPoint));				
			}else if(e.targetType ==  CollidableType.GEM){
				_soundController.playCollectGem();
				bot.controller.onCollectGem(new CollectGemEvent(e.collisionPoint, e.gem.bonus));
				e.gem.isCollected = true;
				_view.removeGemView(e.gem);
				return;// no sparck effect
			}else if(e.targetType ==  CollidableType.BOMB){
				_soundController.playHitBomb();
				bot.controller.onHitBomb(new HitBombEvent(e.collisionPoint, e.bomb.damage));
				e.bomb.hasExploded = true;
				_view.removeBombView(e.bomb);
			}
			
			// Spark effect
			var point:Point = new Point();
			point.x = botModel.body.GetPosition().x * GameView.SCALE_FACTOR - 15;
			point.y = botModel.body.GetPosition().y * GameView.SCALE_FACTOR - 15;
			_view.fx.addSpark(point, 0xFFFF00);
		} 
		
		private function updateValues():void
		{
			var bot:BaseBot;
			var botModel:BotModel;
			var enginePower:Number;
			var steeringPower:Number;
			
			for each (botModel in _model.botList)
			{
				bot = botModel.bot;
				enginePower = bot.controller.getEnginePower();
				steeringPower = bot.controller.getSteeringPower();
				
				// Limits
				if (enginePower > 1) enginePower = 1;
				else if (enginePower < -1) enginePower = -1;
				if (steeringPower > 1) steeringPower = 1;
				else if (steeringPower < -1) steeringPower = -1;
				
				botModel.enginePower = enginePower;
				botModel.steeringPower = steeringPower;
				if(bot.dropBombRequest){
					var x:Number = botModel.body.GetPosition().x;
					var y:Number = botModel.body.GetPosition().y;
					var angle:Number = botModel.body.GetAngle();
					var size:Number = b2CircleShape(botModel.body.GetFixtureList().GetShape()).GetRadius();
					var xx:Number = x - 1.5*size*Math.cos(angle);
					var yy:Number = y - 1.5*size*Math.sin(angle);
					var bm:BombModel = addBomb(400, new Point(xx,yy), 0xFF0000);
					_view.addBombView(bm.sprite, bm);
				}
			}
		}
		
		
		private function updateComponents():void
		{
			for each (var botModel:BotModel in _model.botList)
			{
				_componentManager.updateBot(botModel);
			}
		}
		
		
		private function updateBotControllers():void
		{
			var botModel:BotModel;
			
			for each (botModel in _model.botList)
			{
				botModel.bot.controller.update(new BotData(botModel.energy / HealthComponent.START_ENERGY, MathUtils.normalizedAngle(botModel.body.GetAngle())));
			}
		}
		
		
		private function endGame(e:MatchEndEvent=null):void
		{
			dispatchEvent(e.clone());
			stopGame();
			_model.started = false;
			_model.isFinished = true;
			_model.updateWorld();
			_view.update();
			removeEventListener(Event.ENTER_FRAME, update);
			_soundController.playWinner();
		}
		
	}
}